/*
 * TowerFireControl.cs
 *
 * Created on January 5, 2008, 4:10 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * Copyright (C) 2010       Mathijs Miermans
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Controls;

using HexTD.hex;
using HexTD.util;
using HexTD.sprite;

namespace HexTD.tower
{
	public class TowerFireControl : TowerBase, TowerListener
	{

		public static int price = 0;
		public static int damage = 0;
		public static float range = 2.5f;

		private Color line2Color;
		private Color transColor;

		private int mode1 = 0;
		private int mode2 = 0;

		private List<Tower> clients;
		private LineSegment[] clientLines;
		private Brush clientStroke;
		private Color clientColor;

		private Dictionary<Tower, EnemyData> targetTable = new Dictionary<Tower, EnemyData>();
		private EnemyData[] enemyData;
		private CompareCloseSame compCloseSame = new CompareCloseSame();
		private CompareCloseDiff compCloseDiff = new CompareCloseDiff();
		private CompareHardSame compHardSame = new CompareHardSame();
		private CompareHardDiff compHardDiff = new CompareHardDiff();
		private CompareWeakSame compWeakSame = new CompareWeakSame();
		private CompareWeakDiff compWeakDiff = new CompareWeakDiff();
		private CompareFarSame compFarSame = new CompareFarSame();
		private CompareFarDiff compFarDiff = new CompareFarDiff();
		string[] modes2 = { "Same", "Diff", "Diff++" };
		string[] modes1 = { "Close", "Hard", "Weak", "Furthest" };

		private class EnemyData
		{
			public EnemySprite enemy;
			public float dist2;
			public float health;
			public float progress;
			public int towerCount;
			public int nr;
			public EnemyData(EnemySprite e, int i)
			{
				this.enemy = e;
				this.nr = i;
			}
		}

		private class CompareCloseSame : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e2.towerCount - e1.towerCount;
				}
				else
				{
					return (int)(e1.dist2 - e2.dist2);
				}
			}
		}
		private class CompareCloseDiff : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e1.towerCount - e2.towerCount;
				}
				else
				{
					return (int)(e1.dist2 - e2.dist2);
				}
			}
		}
		private class CompareHardSame : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e2.towerCount - e1.towerCount;
				}
				else
				{
					return (int)(e2.health - e1.health);
				}
			}
		}
		private class CompareHardDiff : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e1.towerCount - e2.towerCount;
				}
				else
				{
					return (int)(e2.health - e1.health);
				}
			}
		}
		private class CompareWeakSame : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e2.towerCount - e1.towerCount;
				}
				else
				{
					return (int)(e1.health - e2.health);
				}
			}
		}
		private class CompareWeakDiff : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e1.towerCount - e2.towerCount;
				}
				else
				{
					return (int)(e1.health - e2.health);
				}
			}
		}
		private class CompareFarSame : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e2.towerCount - e1.towerCount;
				}
				else
				{
					return (int)((e2.progress - e1.progress) * 1000);
				}
			}
		}
		private class CompareFarDiff : IComparer<EnemyData>
		{
			public int Compare(EnemyData e1, EnemyData e2)
			{
				if (e1.towerCount != e2.towerCount)
				{
					return e1.towerCount - e2.towerCount;
				}
				else
				{
					return (int)((e2.progress - e1.progress) * 1000);
				}
			}
		}

		/** Creates a new instance of TowerFireControl */
		public TowerFireControl(Context context, Hex hex)
			: base(TowerFactory.type.firecontrol, price, damage, range)
		{
			this.lineColor = Color.FromArgb(255, 200, 200, 200);
			this.line2Color = Color.FromArgb(255, 255, 200, 200);
			this.clientColor = Color.FromArgb(255, 255, 200, 200);
			this.transColor = Color.FromArgb(100, this.lineColor.R, this.lineColor.G, this.lineColor.B);
			this.clients = new List<Tower>();

			this.doInit(context, hex);

			this.context.addTowerListener(this);
			this.context.waveStarted += new EventHandler(waveStarted);
			this.enemyData = new EnemyData[0];

			this.scanTowers();
			this.waveStarted(this, null);
		}

		protected override void doInit(Context context, Hex hex)
		{
			this.context = context;
			this.rangeColor = this.context.colors.getColorRangeCircle();
			this.damageCurrent = this.damageBase;

			this.hex = hex;
			if (hex != null)
			{
				this.hexLocation = hex.getLocation();
			}

			PanelTowerSettingsDouble p = new PanelTowerSettingsDouble(this, this.context);
			p.setModes1(this.modes1);
			p.setModes2(this.modes2);
			this.pts = p;
			this.resetScale();
		}

		private void scanTowers()
		{
			if (!this.selling)
			{
				float dx, dy;
				float[] loc;
				foreach (Tower t in this.context.towers)
				{
					if (!this.clients.Contains(t))
					{
						switch (t.getType())
						{
							case TowerFactory.type.firecontrol:
							case TowerFactory.type.upgradeDamage:
							case TowerFactory.type.upgradeRange:
								break;
							default:
								loc = t.getHex().getFloatLocation();
								dx = this.xFloat - loc[0];
								dy = this.yFloat - loc[1];
								if ((dx * dx + dy * dy) < this.rangeCurrent2)
								{
									t.registerTower(this);
								}
								break;
						}
					}
					else
					{
						loc = t.getHex().getFloatLocation();
						dx = this.xFloat - loc[0];
						dy = this.yFloat - loc[1];
						if ((dx * dx + dy * dy) > this.rangeCurrent2)
						{
							t.unregisterTower(this);
						}
					}
				}
			}
		}

		protected override void calcDamageRange()
		{
			base.calcDamageRange();
			this.scanTowers();
		}

		public override bool canUpgrade()
		{
			return false;
		}

		public override string getStatusString()
		{
			string retString = "";
			retString += "<h1>Fire Control</h1>" +
					"<p>" +
					"Controls the targeting of other towers<br>" +
					"<b>Towers controlled:</b> " + this.clients.Count + "<br>\n" +
					"<b>Range:</b> " + this.rangeText + "<br>" +
					"</p>" +
					"<p><b>R</b>: Next control mode</p>\n";
			return retString;
		}

		public override string getInfoString()
		{
			string retString = "";
			retString += "<h1>Fire Control</h1>" +
					"<p>" +
					"This tower controls the targeting of other towers and can force them to fire at the same, or different targets. " +
					"It has no effect on Blue Towers 1 & 2." +
					"</p><p class='warn'>This tower costs 1 bonus point.</p>";
			return retString;
		}

		public override void doTick(int gameTime)
		{
		}

		public void towerBuild(Tower t)
		{
			if (t != this && !this.selling && !this.clients.Contains(t))
			{
				switch (t.getType())
				{
					case TowerFactory.type.firecontrol:
					case TowerFactory.type.upgradeDamage:
					case TowerFactory.type.upgradeRange:
						break;
					default:
						float dx, dy;
						float[] loc;
						loc = t.getHex().getFloatLocation();
						dx = this.xFloat - loc[0];
						dy = this.yFloat - loc[1];
						if ((dx * dx + dy * dy) < this.rangeCurrent2)
						{
							t.registerTower(this);
						}
						break;
				}
			}
		}

		public void towerRemoved(Tower t)
		{
			if (this.clients.Contains(t))
			{
				t.unregisterTower(this);
			}
		}

		public void addClient(Tower t)
		{
			if (!this.selling && !this.clients.Contains(t))
			{
				this.clients.Add(t);
				this.generateShapes();
			}
		}
		public void removeClient(Tower t)
		{
			this.clients.Remove(t);
			this.generateShapes();
		}

		protected override void generateShapes()
		{
#if PORT
			Cache cache = this.context.getCache();

			float width = this.context.hexPaintWidth;
			int shapeCount = 13;
			int i;

			float cx = +this.paintScale * 1 / 4;
			float cy = -this.paintScale / 5;
			float[,] points1 = Geometry.createPolyCoords(this.paintScale / 2, cx, cy);
			float[,] points2 = Geometry.createPolyCoords(this.paintScale);

			i = 0;
			this.shapes = new Shape[shapeCount];
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[0, 0], points1[0, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[1, 0], points1[1, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[2, 0], points1[2, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[3, 0], points1[3, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[4, 0], points1[4, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points1[5, 0], points1[5, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points2[0, 0], points2[0, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points2[1, 0], points2[1, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points2[2, 0], points2[2, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points2[3, 0], points2[3, 1]);
			this.shapes[i++] = new LineSegment.Float(cx, cy, points2[5, 0], points2[5, 1]);
			this.shapes[i++] = Geometry.createCircle(cx, cy, this.paintScale / 2);
			this.shapes[i++] = Geometry.createCircle(cx, cy, this.paintScale / 2);

			this.shapeAction = new int[shapeCount];
			for (i = 0; i < shapeCount; i++)
			{
				this.shapeAction[i] = 1;
			}
			this.shapeAction[11] = 0;

			this.shapeColor = new Color[shapeCount];
			for (i = 0; i < 6; i++)
			{
				this.shapeColor[i] = this.line2Color;
			}
			for (i = 6; i < shapeCount; i++)
			{
				this.shapeColor[i] = this.lineColor;
			}
			this.shapeColor[11] = this.transColor;

			i = 0;
			this.strokes = new Brush[shapeCount];
			this.strokes[i++] = new Brush(1.5f * this.context.strokeScale, Brush.CAP_ROUND, Brush.JOIN_BEVEL);
			this.strokes[i++] = this.strokes[0];
			this.strokes[i++] = this.strokes[0];
			this.strokes[i++] = this.strokes[0];
			this.strokes[i++] = this.strokes[0];
			this.strokes[i++] = this.strokes[0];
			this.strokes[i++] = new Brush(1.0f * this.context.strokeScale, Brush.CAP_ROUND, Brush.JOIN_BEVEL);
			this.strokes[i++] = this.strokes[6];
			this.strokes[i++] = this.strokes[6];
			this.strokes[i++] = this.strokes[6];
			this.strokes[i++] = this.strokes[6];
			this.strokes[i++] = this.strokes[6];
			this.strokes[i++] = this.strokes[6];

			this.clientLines = new LineSegment[this.clients.Count];
			float[] from;
			float[] to = this.getHex().getPaintLocation();
			to[0] += cx;
			to[1] += cy;
			for (i = 0; i < this.clientLines.Length; i++)
			{
				from = this.clients[i].getHex().getPaintLocation();
				this.clientLines[i] = new LineSegment.Float(from[0], from[1], to[0], to[1]);
			}
			this.clientStroke = new Brush(1.0f * this.context.strokeScale, Brush.CAP_ROUND, Brush.JOIN_BEVEL);
#endif
		}

		public override void paintEffects(Canvas g2, int gameTime)
		{
#if PORT
			g2.setStroke(this.clientStroke);
			g2.setColor(this.clientColor);
			for (int i = 0; i < this.clientLines.Length; i++)
			{
				g2.draw(this.clientLines[i]);
			}
#endif
		}

		public override void doCleanup()
		{
			base.doCleanup();
			for (int i = this.clients.Count - 1; i >= 0; i--)
			{
				Tower t = this.clients[i];
				t.unregisterTower(this);
			}
			this.context.removeTowerListener(this);
		}

		public void waveStarted(object sender, EventArgs e)
		{
			if (this.context.enemies != null)
			{
				this.enemyData = new EnemyData[this.context.enemies.Length];
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					this.enemyData[i] = new EnemyData(this.context.enemies[i], i);
				}
			}
			else
			{
				this.enemyData = new EnemyData[0];
			}
			this.targetTable.Clear();
		}

		public override bool setMode(int m, int v)
		{
			if (m == 1 && this.mode1 != v)
			{
				if (v >= this.modes1.Length)
				{
					v = 0;
				}
				this.mode1 = v;

				this.retarget = true;
				System.Diagnostics.Debug.WriteLine("TowerFireControl::setMode: Setting to " + this.modes1[this.mode1]);
				if (this.pts.getMode(m) != v)
				{
					this.pts.setMode(m, v);
				}
				return true;
			}
			else if (m == 2 && this.mode2 != v)
			{
				if (v >= this.modes2.Length)
				{
					v = 0;
				}
				this.mode2 = v;

				this.retarget = true;
				System.Diagnostics.Debug.WriteLine("TowerFireControl::setMode: Setting to " + this.modes2[this.mode2]);
				if (this.pts.getMode(m) != v)
				{
					this.pts.setMode(m, v);
				}
				return true;
			}
			else if (m != 1 && m != 2)
			{
				System.Diagnostics.Debug.WriteLine("TowerFireControl::setMode: unknown mode: " + m);
				return false;
			}
			return true;
		}

		public override int getMode(int m)
		{
			if (m == 1)
			{
				return this.mode1;
			}
			else if (m == 2)
			{
				return this.mode2;
			}
			else
			{
				System.Diagnostics.Debug.WriteLine("TowerBase::setMode: unknown mode: " + m);
				return -1;
			}
		}

		public void updateEnemies(float x, float y)
		{
			float dx, dy;
			for (int i = 0; i < this.enemyData.Length; i++)
			{
				dx = x - this.enemyData[i].enemy.getX();
				dy = y - this.enemyData[i].enemy.getY();
				enemyData[i].dist2 = dx * dx + dy * dy;
				enemyData[i].health = enemyData[i].enemy.getHealth();
				enemyData[i].progress = enemyData[i].enemy.getProgress();
			}
		}

		public int findEnemy(Tower t, int preferedEnemyNr, bool exShielded)
		{
			if (this.retarget)
			{
				preferedEnemyNr = -1;
				this.retarget = false;
			}
			switch (this.mode1)
			{
				case 0:
					return this.findEnemyClosest(t, preferedEnemyNr, exShielded);
				case 1:
					return this.findEnemyHardest(t, preferedEnemyNr, exShielded);
				case 2:
					return this.findEnemyWeakest(t, preferedEnemyNr, exShielded);
				case 3:
					return this.findEnemyFurthest(t, preferedEnemyNr, exShielded);
				default:
					System.Diagnostics.Debug.WriteLine("TowerFireControl::FindEnemy: Unknown mode1 " + this.mode1);
					break;
			}
			return -1;
		}

		private int findEnemyClosest(Tower t, int preferedEnemyNr, bool exShielded)
		{
			return findEnemy(t, preferedEnemyNr, exShielded, this.compCloseSame, this.compCloseDiff);
		}
		private int findEnemyHardest(Tower t, int preferedEnemyNr, bool exShielded)
		{
			return findEnemy(t, preferedEnemyNr, exShielded, this.compHardSame, this.compHardDiff);
		}
		private int findEnemyWeakest(Tower t, int preferedEnemyNr, bool exShielded)
		{
			return findEnemy(t, preferedEnemyNr, exShielded, this.compWeakSame, this.compWeakDiff);
		}
		private int findEnemyFurthest(Tower t, int preferedEnemyNr, bool exShielded)
		{
			return findEnemy(t, preferedEnemyNr, exShielded, this.compFarSame, this.compFarDiff);
		}

		private int findEnemy(Tower t, int preferedEnemyNr, bool exShielded, IComparer<EnemyData> cSame, IComparer<EnemyData> cDiff)
		{
			float r2 = t.getRangeCurrent();
			r2 *= r2;
			float[] loc = t.getHex().getFloatLocation();
			this.updateEnemies(loc[0], loc[1]);

			bool force = false;
			EnemySprite e;
			EnemyData ed, oldEd;
			oldEd = this.targetTable[t];

			switch (this.mode2)
			{
				case 0:
					Array.Sort(this.enemyData, cSame);
					break;
				case 2:
					force = true;
					goto case 1;
				case 1:
					if ((oldEd != null) && (oldEd.dist2 < r2) && (oldEd.towerCount == 1))
					{
						if (oldEd.enemy.validTarget() && !(exShielded && oldEd.enemy.isShielded()))
						{
							return oldEd.nr;
						}
						else
						{
							oldEd.towerCount--;
							this.targetTable.Remove(t);
							oldEd = null;
						}
					}
					Array.Sort(this.enemyData, cDiff);
					break;
			}

			for (int i = 0; i < this.enemyData.Length; i++)
			{
				ed = this.enemyData[i];
				e = ed.enemy;
				if (e.validTarget() && !(exShielded && e.isShielded()))
				{
					if (ed.dist2 < r2 && !(force && ed.towerCount > 0))
					{
						// this is our target!
						if (ed != oldEd)
						{
							// we changed targets!
							if (oldEd != null)
							{
								oldEd.towerCount--;
							}
							ed.towerCount++;
							this.targetTable.Add(t, ed);
						}
						return ed.nr;
					}
				}
			}
			if (oldEd != null)
			{
				oldEd.towerCount--;
				this.targetTable.Remove(t);
			}
			return -1;
		}

	}
}